# -*- coding: utf-8 -*-

from ir.ir_statement import *
from ir.ir_value import *
from ir.ir_ds import *

from hbcxx.cxx_format import *
from hbcxx.simcxx_vars import *
from hbcxx.simcxx_types import *


def simcxx_get_string(ir_str: IValueStr):
    return ir_str.value


def simcxx_convert_member(ir_member: IExpMember):
    # Convert the parent.
    cxx_parent = simcxx_convert_exp(ir_member.parent)
    # Check the parent type.
    if isinstance(ir_member.parent, IVarPort):
        # Then it must be a pointer.
        return '{}->{}'.format(cxx_parent, simcxx_convert_exp(ir_member.attr))
    if isinstance(ir_member.parent, IVarTemp) or \
            isinstance(ir_member.parent, IExpSubscript) or \
            isinstance(ir_member.parent, IVarReg) or \
            isinstance(ir_member.parent, IExpMember):
        # Need to check variable type.
        var_type = ir_member.parent.type
        if isinstance(var_type, ITypePort) or isinstance(var_type, ITypeNode):
            return '{}->{}'.format(cxx_parent, simcxx_convert_exp(ir_member.attr))
        if isinstance(var_type, ITypeStruct):
            return '{}.{}'.format(cxx_parent, simcxx_convert_exp(ir_member.attr))
        raise Exception('Unknown parent variable type {}.'.format(type(var_type)))
    if isinstance(ir_member.parent, IVarNode):
        return '{}->{}'.format(cxx_parent, simcxx_convert_exp(ir_member.attr))
    raise Exception('Unknown member parent type {}.'.format(type(ir_member.parent)))


def simcxx_create_funccall(func, params):
    # Convert the parameters.
    cxx_params = []
    for ir_param in params:
        cxx_params.append(simcxx_convert_exp(ir_param))
    # Construct the parameters.
    cxx_params = ', '.join(cxx_params)
    # For all ports, they are pointers, so call the function.
    return '{}({})'.format(func, cxx_params)


def simcxx_convert_call(ir_call: IOpCall):
    # Get the function definition.
    funcdef = ir_call.funcdef
    # Check the parent of the function.
    if isinstance(funcdef.parent, IExpThis):
        # For all the port calling this, it should be only several functions.
        funcname = funcdef.func_name
        if funcname == 'port_in_direct':
            manage_type = simcxx_get_type(ir_call.params[0])
            return 'new PortInDirect<{}>(this)'.format(manage_type)
        if funcname == 'port_out':
            manage_type = simcxx_get_type(ir_call.params[0])
            return 'new PortOut<{}>(this)'.format(manage_type)
        if funcname == 'port_channel_sender':
            channel_type = simcxx_get_type(ir_call.params[0])
            return 'new {}Sender(this)'.format(channel_type)
        if funcname == 'port_channel_receiver':
            channel_type = simcxx_get_type(ir_call.params[0])
            return 'new {}Receiver(this)'.format(channel_type)
        if funcname == 'port_bypass':
            return simcxx_convert_exp(ir_call.params[0])
        if funcname == 'node_create':
            # First, fetch the node type.
            node_type = simcxx_remove_pointer_type(simcxx_get_type(ir_call.params[0]))
            # Create the parameters.
            cxx_func_param = 'this'
            # Then, we need to check its parameter.
            # Get the node construction parameters.
            ir_params = ir_call.params[1].value.value
            if len(ir_params) > 0:
                # Prepare the temp variable.
                cxx_param_var = 'node_param_{}'.format(id(ir_call))
                # It has parameter, need to construct parameter struct.
                node_params = ['{}Param {};'.format(node_type, cxx_param_var)]
                for param_name in ir_params:
                    cxx_param_exp = simcxx_convert_exp(ir_params[param_name])
                    if isinstance(cxx_param_exp, tuple):
                        cxx_param_exp, cxx_predef = cxx_param_exp
                        node_params += cxx_predef
                    node_params.append('{}.{} = {};'.format(
                        cxx_param_var, param_name, cxx_param_exp))
                # Append the parameter.
                cxx_func_param += ', {}'.format(cxx_param_var)
            else:
                # Just simply create a node.
                return 'new {}(this)'.format(node_type)
            return 'new {}({})'.format(node_type, cxx_func_param), node_params
        raise Exception('Unimplemented this function {}.'.format(funcname))
    if isinstance(funcdef.parent, IVarPort) or \
            isinstance(funcdef.parent, IVarTemp) or \
            isinstance(funcdef.parent, IExpSubscript):
        # It must be a port function calling.
        return simcxx_create_funccall('{}->{}'.format(simcxx_convert_exp(funcdef.parent), funcdef.func_name),
                                      ir_call.params)
    if isinstance(funcdef.parent, IExpMember):
        # Need to track down the member.
        return simcxx_create_funccall('{}->{}'.format(simcxx_convert_member(funcdef.parent), funcdef.func_name),
                                      ir_call.params)
    if isinstance(funcdef.parent, ITypeChannel):
        # Okay, it should call the class level method.
        return simcxx_create_funccall('{}::{}'.format(simcxx_get_type(funcdef.parent), funcdef.func_name),
                                      ir_call.params)
    raise Exception('Unknown parent function call {}.'.format(type(funcdef.parent)))


def simcxx_convert_list(ir_exp: IValueList):
    cxx_temp_var = 'tmp_{}'.format(id(ir_exp))
    cxx_list_type = simcxx_get_type(ir_exp.type)
    # Construct the list.
    cxx_list = ['{} {} = {}();'.format(cxx_list_type, cxx_temp_var, cxx_list_type)]
    # Append the data to list.
    for ir_value in ir_exp.value:
        # Convert the value.
        cxx_value = simcxx_convert_exp(ir_value)
        if isinstance(cxx_value, str):
            cxx_list.append('{}.append({});'.format(cxx_temp_var, cxx_value))
        else:
            raise Exception('Unknown value type {}.'.format(type(cxx_value)))
    return cxx_temp_var, cxx_list


def simcxx_convert_value(ir_exp: IValue):
    if isinstance(ir_exp, IValueInt):
        return str(ir_exp.value)
    if isinstance(ir_exp, IValueBool):
        return 'true' if ir_exp.value else 'false'
    if isinstance(ir_exp, IValueComplex):
        c_value = ir_exp.value
        return 'std::complex<float>({}, {})'.format(c_value.real, c_value.imag)
    if isinstance(ir_exp, IVarPort):
        return simcxx_get_cxx_port(ir_exp.name)
    if isinstance(ir_exp, IVarTemp):
        return ir_exp.name
    if isinstance(ir_exp, IValueList):
        return simcxx_convert_list(ir_exp)
    raise Exception('Unimplemented value type: {}'.format(type(ir_exp)))


def simcxx_convert_var(ir_exp: IVar):
    if isinstance(ir_exp, IVarPort):
        return simcxx_get_cxx_port(ir_exp.name)
    if isinstance(ir_exp, IVarReg):
        return simcxx_get_cxx_reg(ir_exp.name)
    if isinstance(ir_exp, IVarConst):
        return simcxx_get_cxx_const(ir_exp.name)
    if isinstance(ir_exp, IVarNode):
        return simcxx_get_cxx_node(ir_exp.name)
    if isinstance(ir_exp, IVarParam):
        return 'params.{}'.format(ir_exp.name)
    if isinstance(ir_exp, IVarTemp):
        return ir_exp.name
    raise Exception('Unimplemented variable type: {}'.format(type(ir_exp)))


def simcxx_convert_unary(ir_exp: IExpUnary):
    return '{}({})'.format(ir_exp.op, simcxx_convert_exp(ir_exp.operand))


def simcxx_convert_binary(ir_exp: IExpBinary):
    return '({}){}({})'.format(simcxx_convert_exp(ir_exp.left),
                               ir_exp.op,
                               simcxx_convert_exp(ir_exp.right))


def simcxx_convert_exp(ir_exp):
    if isinstance(ir_exp, IValueListComp):
        return simcxx_convert_listcomp(ir_exp)
    if isinstance(ir_exp, IValue):
        return simcxx_convert_value(ir_exp)
    if isinstance(ir_exp, IVar):
        return simcxx_convert_var(ir_exp)
    if isinstance(ir_exp, IExpValue):
        return simcxx_convert_exp(ir_exp.value)
    if isinstance(ir_exp, IExpSubscript):
         # Convert the parent data.
         parent = simcxx_convert_exp(ir_exp.parent)
         index = simcxx_convert_exp(ir_exp.index)
         return '{}[{}]'.format(parent, index)
    if isinstance(ir_exp, IOpCall):
        return simcxx_convert_call(ir_exp)
    if isinstance(ir_exp, IExpUnary):
        return simcxx_convert_unary(ir_exp)
    if isinstance(ir_exp, IExpBinary):
        return simcxx_convert_binary(ir_exp)
    if isinstance(ir_exp, IExpMember):
        return simcxx_convert_member(ir_exp)
    if isinstance(ir_exp, IOpIfExp):
        return simcxx_convert_ifexp(ir_exp)
    if isinstance(ir_exp, IOpLen):
        return '{}.size()'.format(simcxx_convert_exp(ir_exp.var))
    if isinstance(ir_exp, IOpTypeConvert):
        src_var = simcxx_convert_exp(ir_exp.var)
        dst_type = simcxx_get_type(ir_exp.type)
        return 'static_cast<{}>({})'.format(dst_type, src_var)
    if isinstance(ir_exp, IOpConstruct):
        return '{}()'.format(simcxx_get_type(ir_exp.type))
    if isinstance(ir_exp, int):
        return str(ir_exp)
    if isinstance(ir_exp, str):
        return ir_exp
    raise Exception('Unimplemented IR exp convert: {}'.format(type(ir_exp)))


def simcxx_extract_range(ir_range: IOpRange):
    return simcxx_convert_exp(ir_range.start), \
           simcxx_convert_exp(ir_range.end), \
           simcxx_convert_exp(ir_range.step)


def simcxx_convert_ifexp(ir_ifexp: IOpIfExp):
    # Convert the condition.
    cxx_condition = simcxx_convert_exp(ir_ifexp.condition)
    cxx_exp_true = simcxx_convert_exp(ir_ifexp.body)
    cxx_exp_false = simcxx_convert_exp(ir_ifexp.elbody)
    # Construct the expression.
    return '({}) ? ({}) : ({})'.format(cxx_condition, cxx_exp_true, cxx_exp_false)


def simcxx_transfer_assign(cxx_variable, ir_exp):
    # Check the exp type.
    if isinstance(ir_exp, IOpCall):
        # Get the function call.
        cxx_funccall = simcxx_convert_call(ir_exp)
        if isinstance(cxx_funccall, tuple):
            # Extract the pre-defintion.
            cxx_funccall, cxx_predef = cxx_funccall
            cxx_predef = cxx_predef
        else:
            cxx_predef = []
        # Give the assignment to the variable.
        return cxx_predef + ['{} = {};'.format(cxx_variable, cxx_funccall)]
    if isinstance(ir_exp, IExp) or isinstance(ir_exp, IOpIfExp) or \
            isinstance(ir_exp, IVarTemp) or isinstance(ir_exp, IVar) or \
            isinstance(ir_exp, IOpConstruct) or \
            isinstance(ir_exp, IValueListComp):
        cxx_exp = simcxx_convert_exp(ir_exp)
        if isinstance(cxx_exp, str):
            return '{} = {};'.format(cxx_variable, cxx_exp)
        elif isinstance(cxx_exp, tuple):
            cxx_exp, cxx_predef = cxx_exp
            return [*cxx_predef, '{} = {};'.format(cxx_variable, cxx_exp)]
        else:
            raise Exception('Unknown expression {}.'.format(type(cxx_exp)))
    raise Exception('Unimplemented variable assignment type {}.'.format(type(ir_exp)))


def simcxx_convert_listcomp(ir_listcomp: IValueListComp):
    # Get the manage list.
    ir_listtype = ir_listcomp.type
    cxx_iter_var = simcxx_convert_exp(ir_listcomp.iter_var)
    # First, we need to create a list.
    r_start, r_end, r_step = simcxx_extract_range(ir_listcomp.iter_range)
    # Create a temp variable.
    cxx_temp_var = 'tmp_{}'.format(id(ir_listcomp))
    cxx_temp_type = simcxx_get_type(ir_listtype)
    # Convert all the initial codes.
    cxx_init = simcxx_convert_body_content(ir_listcomp.iter_init)
    # Convert the assign.
    cxx_item_construct = simcxx_convert_exp(ir_listcomp.exp)
    if isinstance(cxx_item_construct, str):
        cxx_item_construct = ['{}.append({});'.format(cxx_temp_var,
                                                      cxx_item_construct)]
    elif isinstance(cxx_item_construct, tuple):
        # Extract the predef.
        cxx_item_construct, cxx_predef = cxx_item_construct
        # Construct the new list.
        cxx_item_construct = [*cxx_predef,
                              '{}.append({});'.format(cxx_temp_var,
                                                      cxx_item_construct)]
    # Add initial codes before the construct code.
    cxx_item_construct = cxx_init + cxx_item_construct
    # Add indent to each line.
    for ii in range(len(cxx_item_construct)):
        cxx_item_construct[ii] = CXX_INDENT + cxx_item_construct[ii]
    cxx_listcomp = ['{} {};'.format(cxx_temp_type, cxx_temp_var),
                    'for(int {}={}; {}<{}; {}+={})'.format(
                        cxx_iter_var, r_start, cxx_iter_var, r_end, cxx_iter_var, r_step),
                    '{',
                    *cxx_item_construct,
                    '}']
    return cxx_temp_var, cxx_listcomp


def simcxx_convert_assign(ir_assign: IOpAssign):
    cxx_target = simcxx_convert_exp(ir_assign.target)
    # Check the target variable type.
    if isinstance(ir_assign.target, IVarTemp):
        cxx_target = '{} {}'.format(simcxx_get_type(ir_assign.target.type),
                                    cxx_target)
    # Transfer the assignment.
    cxx_assign = simcxx_transfer_assign(cxx_target, ir_assign.exp)
    if not isinstance(cxx_assign, list):
        return [cxx_assign]
    return cxx_assign


def simcxx_convert_augassign(ir_assign: IOpAugAssign):
    # Convert the left hard.
    cxx_target = simcxx_convert_exp(ir_assign.target)
    cxx_exp = simcxx_convert_exp(ir_assign.exp)
    # Construct the assignment.
    return ['{} {}= {};'.format(cxx_target, ir_assign.op, cxx_exp)]


def simcxx_convert_if(ir_if: IOpIf):
    # Convert the body.
    if_body = simcxx_convert_body(ir_if.body)
    # Add indent to body.
    for ii in range(len(if_body)):
        if_body[ii] = CXX_INDENT + if_body[ii]
    # Convert the condition.
    cxx_condition = simcxx_convert_exp(ir_if.condition)
    # Create the if structure.
    cxx_if = ['if({})'.format(cxx_condition),
              '{',
              *if_body,
              '}']
    # Check for the else body.
    if ir_if.elbody is not None:
        else_body = simcxx_convert_body(ir_if.elbody)
        # Add indent to body.
        for ii in range(len(else_body)):
            else_body[ii] = CXX_INDENT + else_body[ii]
        # Add to body.
        cxx_if += ['else',
                   '{',
                   *else_body,
                   '}']
    return cxx_if


def simcxx_convert_for(ir_for: IOpFor):
    # Convert the body.
    for_body = simcxx_convert_body(ir_for.body)
    # Add indent to body.
    for ii in range(len(for_body)):
        for_body[ii] = CXX_INDENT + for_body[ii]
    # Construct the cxx variable.
    cxx_for_var = ir_for.iter_var
    # Extract the range.
    r_start, r_end, r_step = simcxx_extract_range(ir_for.iter_range)
    # Construct the for-loop.
    cxx_for_loop = ['for(int {}={}; {}<{}; {}+={})'.format(cxx_for_var, r_start, cxx_for_var, r_end, cxx_for_var, r_step),
                    '{',
                    *for_body,
                    '}']
    return cxx_for_loop


def simcxx_convert_body_content(body_ops: list):
    cxx_body = []
    # Loop for ops.
    for ir_exp in body_ops:
        if isinstance(ir_exp, IOpAssign):
            cxx_body += simcxx_convert_assign(ir_exp)
        elif isinstance(ir_exp, IOpCall):
            cxx_body += [simcxx_convert_call(ir_exp) + ';']
        elif isinstance(ir_exp, IOpAugAssign):
            cxx_body += simcxx_convert_augassign(ir_exp)
        elif isinstance(ir_exp, IOpIf):
            cxx_body += simcxx_convert_if(ir_exp)
        elif isinstance(ir_exp, IOpFor):
            cxx_body += simcxx_convert_for(ir_exp)
        elif isinstance(ir_exp, IOpReturn):
            cxx_body += ['return;']
        elif isinstance(ir_exp, IOpBreak):
            cxx_body += ['break;']
        elif isinstance(ir_exp, IOpContinue):
            cxx_body += ['continue;']
        elif isinstance(ir_exp, IOpPass):
            cxx_body += [';']
        else:
            raise Exception('Unimplemented ir convert {}.'.format(type(ir_exp)))
    return cxx_body


def simcxx_convert_body(ir_body: IOpBody):
    # Directly convert the body.
    return simcxx_convert_body_content(ir_body.ops)